%%% $Id$
%%%--------------------------------------------------------------------
%%% Copyright 2008 Vance Shipley
%%% @author Vance Shipley <vances@motivity.ca>
%%% @end
%%%---------------------------------------------------------------------
%%% @doc DVD IFO codec tests
%%%--------------------------------------------------------------------
%%%
-module(dvd_ifo_SUITE).

%% common_test required callbacks
-export([suite/0, init_per_suite/1, end_per_suite/1, sequences/0, all/0]).

-compile(export_all).

-include("../include/dvd_ifo.hrl").

%%---------------------------------------------------------------------
%%  Test server callback functions
%%---------------------------------------------------------------------

%% @spec () -> DefaultData
%% 	DefaultData = [tuple()]
%% @doc Require variables and set default values for the suite.
%%
suite() ->
	[{timetrap, {minutes, 1}}].

%% @spec (Config) -> Config
%% 	Config = [tuple()]
%% @doc Initiation before the whole suite.
%%
init_per_suite(Config) ->
	Config.

%% @spec (Config) -> any()
%% 	Config = [tuple()]
%% @doc Cleanup after the whole suite.
%%
end_per_suite(Config) ->
	Config.

%% @spec () -> Sequences 
%% 	Sequences = [{SeqName, Testcases}]
%% 	SeqName = atom()
%% 	Testcases = [atom()]
%% @doc Group test cases into a test sequence.
%%
sequences() -> 
	[].

%% @spec () -> TestCases
%% 	TestCases = [Case]
%% 	Case = atom()
%% @doc Returns a list of all test cases in this test suite.
%%
all() -> 
	[vmg, vts, first_play_pgc, table_of_titles, menu_program_chain,
	table_of_titles_and_chapters].

%%---------------------------------------------------------------------
%%  Test cases
%%---------------------------------------------------------------------

vmg() ->
	[{userdata, [{doc, "Read the Video Manager Information Table."}]}].

vmg(_Config) ->
	case dvd_read_lib:get_vmg() of
		VMGI when VMGI#video_manager.last_sector_of_set /= 0,
				VMGI#video_manager.last_sector_of_ifo /= 0,
				VMGI#video_manager.number_of_volumes /= 0,
				VMGI#video_manager.volume_number /= 0,
				VMGI#video_manager.volume_number
						=< VMGI#video_manager.number_of_volumes,
				VMGI#video_manager.disc_side > 0,
				VMGI#video_manager.disc_side < 3,
				VMGI#video_manager.number_of_title_sets /= 0,
				VMGI#video_manager.last_byte >= 341,
				VMGI#video_manager.last_byte / 2048
						=< VMGI#video_manager.last_byte,
				VMGI#video_manager.first_play_program_chain
						< VMGI#video_manager.last_byte,
				((VMGI#video_manager.menu_vob == 0) or
						((VMGI#video_manager.menu_vob
						> VMGI#video_manager.last_sector_of_ifo) and
						(VMGI#video_manager.menu_vob
						< VMGI#video_manager.last_sector_of_set))),
				VMGI#video_manager.table_of_titles
						=< VMGI#video_manager.last_sector_of_ifo,
				VMGI#video_manager.menu_program_chain_table
						=< VMGI#video_manager.last_sector_of_ifo,
				VMGI#video_manager.parental_management_masks
						=< VMGI#video_manager.last_sector_of_ifo,
				VMGI#video_manager.title_set_attributes
						=< VMGI#video_manager.last_sector_of_ifo,
				VMGI#video_manager.text_data
						=< VMGI#video_manager.last_sector_of_ifo,
				VMGI#video_manager.menu_cell_address_table
						=< VMGI#video_manager.last_sector_of_ifo,
				VMGI#video_manager.menu_vobu_address_map
						=< VMGI#video_manager.last_sector_of_ifo,
				VMGI#video_manager.menu_number_of_audio_streams =< 1,
				VMGI#video_manager.menu_number_of_subpicture_streams =< 1 ->
			ct:log("Video Manager Information Table = ~s",
					[dvd_read_lib:pretty_print(VMGI)]),
		 	VMGI
	end.

vts() ->
	[{userdata, [{doc, "Read the Video Title Set Information Table."}]}].

vts(_Config) ->
	VideoDir = dvd_read_lib:find_dvd(),
	Files = dvd_read_lib:list_vts(VideoDir),
	F = fun(Path) ->
				VTS = dvd_read_lib:get_vts(VideoDir ++ Path),
				ct:log("~s~nVideo Title Set Information Table = ~s",
						[Path, dvd_read_lib:pretty_print(VTS)]),
				ok = check_vts(VTS),
				VTS
	end,
	[F(Path) || Path <- Files].

first_play_pgc() ->
	[{userdata, [{doc, "Read the First Play Program Chain."}]}].

first_play_pgc(_Config) ->
	IoDevice = dvd_read_lib:open_vmg(),
	VMGIBin = dvd_read_lib:read(IoDevice, 0, ?video_manager_size),
	#video_manager{first_play_program_chain = PGC_offset}
			= dvd_ifo:video_manager(VMGIBin),
  	PGCBin = dvd_read_lib:read(IoDevice, PGC_offset, ?DVD_BLOCK_LEN),
	PGC = dvd_ifo:program_chain(PGCBin),
	ct:log("Program Chain = ~s", [dvd_read_lib:pretty_print(PGC)]),
	check_program_chain(PGC),
	PGC.

table_of_titles() ->
	[{userdata, [{doc, "Read the Table of Titles."}]}].

table_of_titles(_Config) ->
	IoDevice = dvd_read_lib:open_vmg(),
	VMGIBin = dvd_read_lib:read(IoDevice, 0, ?video_manager_size),
	#video_manager{table_of_titles = TT_sector}
			= dvd_ifo:video_manager(VMGIBin),
	TT_offset = TT_sector * ?DVD_BLOCK_LEN,
  	<<Number_of_titles:16, 0:16, Last_byte:32>> =
			dvd_read_lib:read(IoDevice, TT_offset, 8),
	TTBin = dvd_read_lib:read(IoDevice, TT_offset, 12 * Number_of_titles + 8),
	Last_byte = 8 + 12 * Number_of_titles - 1,
	TT = dvd_ifo:table_of_titles(TTBin),
	ct:log("Table of Titles = ~s", [dvd_read_lib:pretty_print(TT)]),
	check_table_of_titles(TT),
	TT.

menu_program_chain() ->
	[{userdata, [{doc, "Read the Menu Program Chain Table."}]}].

menu_program_chain(_Config) ->
	IoDevice = dvd_read_lib:open_vmg(),
	VMGIBin = dvd_read_lib:read(IoDevice, 0, ?video_manager_size),
	#video_manager{menu_program_chain_table = MPC_sector}
			= dvd_ifo:video_manager(VMGIBin),
	MPC_offset = MPC_sector * ?DVD_BLOCK_LEN,
  	<<_Number_of_language_units:16, 0:16, Last_byte:32>> =
			dvd_read_lib:read(IoDevice, MPC_offset, 8),
	MPC_size = Last_byte + 1,
	MPCBin = dvd_read_lib:read(IoDevice, MPC_offset, MPC_size),
	MPC = dvd_ifo:menu_program_chain_table(MPCBin),
	ct:log("Menu Program Chain Table = ~s", [dvd_read_lib:pretty_print(MPC)]),
	check_language_units(MPC),
	MPC.

table_of_titles_and_chapters() ->
	[{userdata, [{doc, "Read the Table of Titles and Chapters"}]}].

table_of_titles_and_chapters(_Config) ->
	VideoDir = dvd_read_lib:find_dvd(),
	Files = dvd_read_lib:list_vts(VideoDir),
	F = fun(Path) ->
				IoDevice = dvd_read_lib:open_file(VideoDir ++ Path),
				VTSIBin = dvd_read_lib:read(IoDevice, 0, ?video_title_set_size),
				#video_title_set{table_of_titles_and_chapters = TTC_sector}
						= dvd_ifo:video_title_set(VTSIBin),
				TTC_offset = TTC_sector * ?DVD_BLOCK_LEN,
				<<_Number_of_titles:16, 0:16, Last_byte:32>>
						= dvd_read_lib:read(IoDevice, TTC_offset, 8),
				TTC_size = Last_byte + 1,
				TTCBin = dvd_read_lib:read(IoDevice, TTC_offset, TTC_size),
				TTC = dvd_ifo:table_of_titles_and_chapters(TTCBin),
				ct:log("~s~nTable of Titles and Chapters = ~s",
						[Path, dvd_read_lib:pretty_print(TTC)]),
			 	ok = check_table_of_titles_and_chapters(TTC),
			 	TTC
	end,
	[F(Path) || Path <- Files].

%%---------------------------------------------------------------------
%%  Internal functions
%%---------------------------------------------------------------------

check_program_chain(PGC) ->
	true = PGC#program_chain.number_of_programs
			=< PGC#program_chain.number_of_cells,
 	F1 = fun(AC) when AC band 16#8000 =:= 0 ->
				0 = AC band 16#7fff;
			(_) -> ok
	end,
	lists:foreach(F1, PGC#program_chain.audio_stream_control),
 	F2 = fun(AC) when AC band 16#80000000 =:= 0 ->
				0 = AC band 16#7fffffff;
			(_) -> ok
	end,
	lists:foreach(F2, PGC#program_chain.subpicture_stream_control),
	case PGC#program_chain.number_of_programs of
		0 when PGC#program_chain.still_time =:= 0,
				PGC#program_chain.program_map =:= 0,
				PGC#program_chain.cell_playback =:= 0,
				PGC#program_chain.cell_position =:= 0 ->
			PGC;
		_ when PGC#program_chain.program_map =/= 0,
				PGC#program_chain.cell_playback =/= 0,
				PGC#program_chain.cell_position =/=0 ->
			PGC
	end.

check_language_units([#language_unit{language_code_extension = 0,
		menu_program_chains = MPCs} | T]) ->
	check_menu_program_chains(MPCs),
	check_language_units(T);
check_language_units([]) ->
	ok.

check_menu_program_chains([#menu_program_chain{program_chain = PGC} | T]) ->
	check_program_chain(PGC),
	check_menu_program_chains(T);
check_menu_program_chains([]) ->
	ok.

check_table_of_titles([TT | T]) ->
	true = TT#title.number_of_angles =/= 0,
	true = TT#title.number_of_angles < 10,
	true = TT#title.number_of_chapters < 1000,
	true = TT#title.title_set_number =/= 0,
	true = TT#title.title_set_number < 100,
	true = TT#title.title_number =/= 0,
	true = TT#title.title_number < 100,
	check_table_of_titles(T);
check_table_of_titles([]) ->
	ok.

check_table_of_titles_and_chapters([TT | T]) ->
	ok = check_table_of_chapters(TT),
	check_table_of_titles_and_chapters(T);
check_table_of_titles_and_chapters([]) ->
	ok.
	
check_table_of_chapters([{ProgramChain, Program} | T]) ->
	true = ProgramChain =/= 0,
	true = ProgramChain < 1000,
	true = Program =/= 0,
	true = Program < 100,
	check_table_of_chapters(T);
check_table_of_chapters([]) ->
	ok.

check_vts(VTS) ->
	true = VTS#video_title_set.last_sector_of_ifo * 2
			=< VTS#video_title_set.last_sector_of_set,
	true = VTS#video_title_set.last_byte / ?DVD_BLOCK_LEN
			=< VTS#video_title_set.last_sector_of_ifo,
	true = ((VTS#video_title_set.menu_vob == 0) or
			((VTS#video_title_set.menu_vob
			> VTS#video_title_set.last_sector_of_ifo) and
			(VTS#video_title_set.menu_vob
			< VTS#video_title_set.last_sector_of_set))),
	true = ((VTS#video_title_set.title_vob == 0) or
			((VTS#video_title_set.title_vob
			> VTS#video_title_set.last_sector_of_ifo) and
			(VTS#video_title_set.title_vob
			< VTS#video_title_set.last_sector_of_set))),
	true = VTS#video_title_set.table_of_titles_and_chapters
			=< VTS#video_title_set.last_sector_of_ifo,
	true = VTS#video_title_set.title_program_chain_table
			=< VTS#video_title_set.last_sector_of_ifo,
	true = VTS#video_title_set.menu_program_chain_table
			=< VTS#video_title_set.last_sector_of_ifo,
	true = VTS#video_title_set.time_map
			=< VTS#video_title_set.last_sector_of_ifo,
	true = VTS#video_title_set.menu_cell_address_table
			=< VTS#video_title_set.last_sector_of_ifo,
	true = VTS#video_title_set.menu_vobu_address_map
			=< VTS#video_title_set.last_sector_of_ifo,
	true = VTS#video_title_set.title_set_cell_address_table
			=< VTS#video_title_set.last_sector_of_ifo,
	true = VTS#video_title_set.title_set_vobu_address_map
			=< VTS#video_title_set.last_sector_of_ifo,
	true = VTS#video_title_set.menu_number_of_audio_streams =< 1,
	true = VTS#video_title_set.menu_number_of_subpicture_streams =< 1,
	ok.
